package fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import fr.upmc.alasca.centreCalcul.interne.machinePhysique.coeur.connectors.CoeurClientConnector;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.coeur.interfaces.CoeurOperateurI;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.coeur.ports.CoeurClientOutboundPort;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.capteurTempsExecution.CapteurTempsExecution;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.capteurTempsExecution.connectors.CapteurTEMachineVConnector;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.capteurTempsExecution.interfaces.CapteurTEMachineVI;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.capteurTempsExecution.ports.CapteurTEMachineVOutboundPort;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.interfaces.MachineVirtuelleApplicationI;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.interfaces.MachineVirtuelleCoeurI;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.ports.MachineVirtuelleApplicationInboundPort;
import fr.upmc.alasca.centreCalcul.interne.machinePhysique.machineVirtuelle.ports.MachineVirtuelleCoeurInboundPort;
import fr.upmc.alasca.centreCalcul.objets.Delais;
import fr.upmc.alasca.centreCalcul.objets.Requete;
import fr.upmc.alasca.centreCalcul.outils.LogMessage;
import fr.upmc.components.AbstractComponent;
import fr.upmc.components.ports.PortI;

/**
 * @author Antoine Lavaire, Sabri Labassi, Thibaut Fleury, Ryme Fantazi
 */
public	class	MachineVirtuelle
extends			AbstractComponent
implements		MachineVirtuelleApplicationI, MachineVirtuelleCoeurI
{
	/* ------------------------------------------------------------- ATTRIBUT */
	//protected int positionAppel = 0;
	//protected String idApplication;

	protected	String										portUri;
	protected	List<Requete>								requetes;
	protected	HashMap<String, CoeurClientOutboundPort>	coeurs;
	protected	List<String>								coeursDisponibles;
	protected	CapteurTEMachineVOutboundPort				capteurTEMV;


	/* --------------------------------------------------------- CONSTRUCTEUR */
	public	MachineVirtuelle
	(
		String	p_portUri,
		Delais	p_fraicheurDonnees
	) throws	Exception
	{
		super(true);

		portUri = p_portUri;
		requetes = new ArrayList<Requete>();
		coeurs = new HashMap<String, CoeurClientOutboundPort>();
		coeursDisponibles = new ArrayList<String>();
		
		LogMessage.printMessage("Virtuelle", portUri,
				"Création de la machine virtuelle.");

		// Ajout de l'interface offerte au répartiteur de requêtes.
		this.addOfferedInterface(MachineVirtuelleApplicationI.class);
		PortI pmva =
				new MachineVirtuelleApplicationInboundPort(portUri + "_Application", this);
		this.addPort(pmva);
		pmva.localPublishPort();

		// Ajout de l'interface offerte aux coeurs.
		this.addOfferedInterface(MachineVirtuelleCoeurI.class);
		PortI pmvc =
				new MachineVirtuelleCoeurInboundPort(portUri + "_Coeur", this);
		this.addPort(pmvc);
		pmvc.localPublishPort();

		// Ajout des interfaces requises pour l'utilisation des coeurs.
		this.addRequiredInterface(CoeurOperateurI.class);

		creerCapteurTE(p_fraicheurDonnees);
	}


	/* -------------------------------------------------------------- METHODE */
	@Override
	public	void	soumettreRequete
	(
		Requete	p_requete
	) throws	Exception
	{
		long t = System.currentTimeMillis();
		p_requete.setHeureArrivee(t);
		
		synchronized(requetes) {
			synchronized(coeursDisponibles) {
				synchronized(coeurs) {
					requetes.add(p_requete);

					// Si on n'a pas de coeur disponible, on attend qu'un vienne se servir.
					if(coeursDisponibles.size() != 0) {
						Requete r = requetes.remove(0);
						coeurs.get(coeursDisponibles.get(0)).executerRequete(r);
						coeursDisponibles.remove(0);
					}
				}
			}
		}
	}


	@Override
	public	void	finExecutionRequete
	(
		String	p_uriCoeur,
		Requete	p_requete,
		long	p_tempsExecution,
		boolean	p_coeurDisponible
	) throws	Exception
	{
		long t = System.currentTimeMillis();
		p_requete.setHeureFinTraitement(t);
		
		synchronized(coeursDisponibles) {
			synchronized(requetes) {
				synchronized(coeurs) {
					if(p_coeurDisponible) {
						if(requetes.size() != 0) {
							Requete r = requetes.remove(0);
							coeurs.get(p_uriCoeur).executerRequete(r);
						}
						else {
							coeursDisponibles.add(p_uriCoeur);
						}
					}

					capteurTEMV.recupererTERequete(p_requete,portUri);
				}
			}
		}
	}


	@Override
	public	void	attributionNouveauCoeur
	(
		String	p_uriCoeur
	) throws	Exception
	{
		LogMessage.printMessage("Virtuelle", portUri,
				"Coeur " + p_uriCoeur + " attribué à la machine virtuelle.");

		CoeurClientOutboundPort cco =
				new CoeurClientOutboundPort(p_uriCoeur + "_ClienOut", this);
		this.addPort(cco);
		cco.localPublishPort();
		cco.doConnection(p_uriCoeur + "_Client",
				CoeurClientConnector.class.getName());
		
		// On ajoute le nouveau coeur à la liste des coeurs de la MV
		synchronized (coeurs) {
			coeurs.put(p_uriCoeur, cco);
		}
		// On ajoute ce coeur dans la liste des coeurs disponibles
		synchronized (coeursDisponibles) {
			coeursDisponibles.add(p_uriCoeur);
		}
	}
	
	
	@Override
	public void retraitCoeur
	(
		String	p_uriCoeur
	) throws	Exception
	{
		LogMessage.printMessage("Virtuelle", portUri,
				"Coeur " + p_uriCoeur + " retiré de la machine virtuelle.");
		
		// Si le coeur est dans la liste des coeurs disponibles, on l'en sort
		synchronized (coeursDisponibles) {
			if (coeursDisponibles.contains(p_uriCoeur))
				coeursDisponibles.remove(p_uriCoeur);
		}
		
		// On retire ensuite le coeur de la liste des coeurs de la machine virtuelle
		synchronized (coeurs) {
			if (coeurs.containsKey(p_uriCoeur))
				coeurs.remove(p_uriCoeur);
		}
	}


	/**
	 * Crée le capteur qui calcule le temps d'execution des requêtes.
	 * @param p_fraicheurDonnees 
	 * @throws Exception
	 */
	private	void	creerCapteurTE
	(
		Delais	p_fraicheurDonnees
	) throws	Exception
	{
		CapteurTempsExecution capteurTE =
				new CapteurTempsExecution(portUri, p_fraicheurDonnees);
		
		capteurTE.start();

		capteurTEMV =
				new CapteurTEMachineVOutboundPort(portUri + "_CapteurTE_Outbound", this);
		this.addPort(capteurTEMV);

		PortI portInbound =
				capteurTE.findPortsFromInterface(CapteurTEMachineVI.class)[0];
		String strPortTEIn = portInbound.getPortURI();

		capteurTEMV.localPublishPort();
		capteurTEMV.doConnection(strPortTEIn, CapteurTEMachineVConnector.class.getCanonicalName());
	}
}
